package org.cos.generation.util.xml;

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.cos.generation.epda.Condition;
import org.cos.generation.epda.EPDA;
import org.cos.generation.epda.Method;
import org.cos.generation.epda.State;
import org.cos.generation.epda.State.Transition;
import org.cos.generation.epda.action.Action;
import org.cos.generation.util.info.ClassInfo;
import org.cos.generation.util.predict.EqualPredictSet;
import org.cos.generation.util.predict.PredictSet;
import org.cos.generation.util.predict.UnionPredictSet;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * This class generate a .model file for every class 
 * coupled with it's the .class file. 
 * @author ZXD
 *
 */

public class ModelOutput {

	/** Store the XML document. */
	private Document document;
	
	/**
	 * Generate .model file for every class.
	 * @param projectPath
	 * @param classInfoList
	 */
	public List<File> generate(String projectPath, List<ClassInfo> classInfoList) {
		List<File> fileList = new ArrayList<File>(classInfoList.size());
		for (ClassInfo classInfo : classInfoList) {
			try {
				EPDA epda = classInfo.getEPDA();
				
				DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
				DocumentBuilder builder = factory. newDocumentBuilder();
				document = builder.newDocument();
				document.setXmlVersion("1.0");
				
				document.appendChild(create(epda));
					
				String fileName = classInfo.getQualifiedName();
				fileName = fileName.replace(".", "//");
				fileName = "//bin//" + fileName;
				fileName = fileName + ".model";
				
				TransformerFactory transFactory=TransformerFactory.newInstance();
				Transformer transformer = transFactory.newTransformer();
				DOMSource domSource=new DOMSource(document);
				File file = new File(projectPath + fileName);
				FileOutputStream out = new FileOutputStream(file);
				StreamResult xmlResult = new StreamResult(out);
				transformer.transform(domSource, xmlResult);
				out.close();
				
				fileList.add(file);
				
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return fileList;
	}
	
	/**
	 * Create a XML element for a EPDA.
	 * @param epda
	 * @return
	 */
	private Element create(EPDA epda) {
		if (epda != null) {
			Element classElement = document.createElement("class");
			Integer i = 0;
			for (State state : epda.getStates()) {
				Element stateElement = create(state);
				stateElement.setAttribute("id", state.getId().toString());
				classElement.appendChild(stateElement);
				i++;
			}
			return classElement;
		}
		return null;
	}
	
	/**
	 * Create a XML element for a state.
	 * @param state
	 * @return
	 */
	private Element create(State state) {
		if (state != null) {
			Element stateElement = document.createElement("state");
			for (Transition transition : state.getTransitions()) {
				stateElement.appendChild(create(transition));
			}
			return stateElement;
		}
		return null;
	}
	
	/**
	 * Create a XML element for a transition.
	 * @param transition
	 * @return
	 */
	private Element create(Transition transition) {
		if (transition != null) {
			Element tranElement = document.createElement("transition");
			
			tranElement.setAttribute("tail", transition.getTailIndex().toString());
			
			Element methodElement = create(transition.getMethod());
			if (methodElement != null) {
				tranElement.appendChild(methodElement);
			}
			
			List<Condition> conditions = transition.getConditions();
			if (conditions != null) {
				Element conditionsElement = document.createElement("conditions");
				for (Condition condition : conditions) {
					Element condElement = create(condition);
					if (condElement != null)
						conditionsElement.appendChild(condElement);
				}
				tranElement.appendChild(conditionsElement);
			}
			
			List<Action> actions = transition.getActions();
			if (actions != null) {
				Element actionsElement = document.createElement("actions");
				for (Action action : actions) {
					actionsElement.appendChild(create(action));
				}
				tranElement.appendChild(actionsElement);
			}
			
			return tranElement;
		}
		return null;
	}
	
	/**
	 * Create a XML element for a method.
	 * @param method
	 * @return
	 */
	private Element create(Method method) {
		if (method != null) {
			Element methodElement = document.createElement("method");
			if (!method.isReturn()) {
				methodElement.setAttribute("name", method.getFullName());
				List<String> paraTypes = method.getParaTypes();
				if (paraTypes != null) {
					for (String para : paraTypes) {
						Element paraElement = document.createElement("parameter");
						paraElement.setAttribute("type", para);
						methodElement.appendChild(paraElement);
					}
				}
			} else {
				methodElement.setAttribute("name", "RETURN");
			}
			return methodElement;
		}
		return null;
	}
	
	/**
	 * Create a XML element for a condition.
	 * @param condition
	 * @return
	 */
	private Element create(Condition condition) {
		if (condition != null && !condition.isAll()) {
			Element condElement = document.createElement("condition");
			
			condElement.setAttribute("type", condition.getType().toString());
			
			Integer target = condition.getTarget();
			if (target != null) {
				condElement.setAttribute("target", target.toString());
			}
			
			Element setsElement = document.createElement("sets");
			List<Element> sets = create(condition.getPredictSet().getSet());
			if (sets != null) {
				for (Element set : sets) {
					setsElement.appendChild(set);
				}
			}
			condElement.appendChild(setsElement);
			
			return condElement;
		}
		return null;
	}
	
	/**
	 * Create a XML element for a predict set.
	 * @param set
	 * @return
	 */
	private List<Element> create(PredictSet set) {
		if (set != null) {
			List<Element> elementList = new LinkedList<Element>();
			switch (set.getType()) {
				case UNION :
					if (!set.isAll()) {
						UnionPredictSet unionSet = (UnionPredictSet) set;
						for (PredictSet child : unionSet.getChilds()) {
							elementList.addAll(create(child));
						}
					}
					break;
				case EQUAL :
					EqualPredictSet equalSet = (EqualPredictSet) set;
					
					Element setElement = document.createElement("set");
					
					setElement.setAttribute("relation", "EQUAL");
					
					Element valueElement = document.createElement("value");
					valueElement.setTextContent(equalSet.getValue().toString());
					setElement.appendChild(valueElement);
					
					elementList.add(setElement);
					break;
			}
			return elementList;
		}
		return null;
	}
	
	/**
	 * Create a XML element for a action.
	 * @param action
	 * @return
	 */
	private Element create(Action action) {
		if (action != null) {
			Element actionElement = document.createElement("action");
			
			actionElement.setAttribute("type", action.getType().toString());
			
			Integer target = action.getTarget();
			if (target != null) {
				actionElement.setAttribute("target", target.toString());
			}
			
			Element operationElement = document.createElement("operation");
			operationElement.setTextContent(action.getOperation().toString());
			actionElement.appendChild(operationElement);
			
			Element valueElement = document.createElement("value");
			valueElement.setTextContent(action.getValue().toString());
			actionElement.appendChild(valueElement);
			
			return actionElement;
		}
		return null;
	}
	
}
